﻿//======================================================================
//
//	XNA ContentBrowser
//
//  by Matt Pettineo (mpettineo@gmail.com)
//  05/03/09  
//
//======================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.IO;
using System.Threading;
using System.Diagnostics;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace ContentBrowser
{

	/// <summary>
	/// Dialog class that displays the ContentBrowser, and allows the user
	/// to browse for and select content.
	/// </summary>
    public partial class ContentBrowserDialog : Form
    {
        private static string contentDirectory;
        private static ContentManager contentManager;
        private static DirectoryNode rootNode;
        private static ProgressDialog progressDialog;

	    /// <summary>
	    /// Traverses the specified content directory for all loadable content, and stores
	    /// it as static data for use when an instance of the Form is created.
	    /// </summary>
	    /// <param name="services">IServiceProvider implementation, contains IGraphicsDeviceService</param>
	    /// <param name="contentDirectory">The content directory to traverse</param>
	    /// <param name="ownerWindow">Owner window for the status dialog</param>
        public static void Initialize(IServiceProvider services, string contentDirectory, IWin32Window ownerWindow)
        {
            ContentBrowserDialog.contentDirectory = contentDirectory;
            rootNode = new DirectoryNode();
			DirectoryInfo info = new DirectoryInfo(contentDirectory);
			rootNode.Path = info.FullName;
            rootNode.Name = info.Name;

            // Make a content manager
            contentManager = new ContentManager(services, contentDirectory);

            progressDialog = new ProgressDialog();
            progressDialog.Shown += new EventHandler(progressDialog_Shown);
            progressDialog.ShowDialog(ownerWindow);
                        
            // Dispose of the content
            contentManager.Dispose();
            contentManager = null;
            GC.Collect();
        }

		/// <summary>
		/// Called when the Shown event is raised by progressDialog.
		/// Starts up the worker thread that enumerates content, and 
		/// pumps messages until it's finished.
		/// </summary>
		/// <param name="sender">The Form that raised the event</param>
		/// <param name="e">Event arguments</param>
        private static void progressDialog_Shown(object sender, EventArgs e)
        {
			Thread thread = new Thread(ThreadStart);
			thread.Start();

			while (!thread.Join(0))
			{
				Application.DoEvents();
				Thread.Sleep(5);
			}

            progressDialog.Close();
        }

		/// <summary>
		/// Entry point for the worker thread.  Calls EnumerateContent.
		/// </summary>
		private static void ThreadStart()
		{
			EnumerateContent(rootNode);
		}

	    /// <summary>
	    /// Enumerates all loadable content for types in contentTypes, and stores
	    /// the resulting tree in contentTrees
	    /// </summary>
        private static void EnumerateContent(DirectoryNode node)
        {
            // Rip through all of the .xnb files
            string[] contentFiles = Directory.GetFiles(node.Path, "*.xnb");
            foreach (string contentFile in contentFiles)
            {
                string contentName = Path.GetFileNameWithoutExtension(contentFile);
                string loadName;
                if (node == rootNode)
                    loadName = contentName;
                else
                    loadName = Path.GetDirectoryName(contentFile).Substring(rootNode.Path.Length + 1) + "\\" + contentName;

                object content = TryLoadContent(loadName);
                if (content != null)
                {
                    ContentNode contentNode = new ContentNode();
                    contentNode.Path = loadName;
                    contentNode.Name = contentName;
                    contentNode.Type = content.GetType();
                    node.ContentNodes.Add(contentNode);
                }
            }

            // Check out the subdirectories
            string[] subdirectories = Directory.GetDirectories(node.Path);
            foreach (string subdirectory in subdirectories)
            {
                string relativePath = subdirectory.Substring(node.Path.Length + 1);
                DirectoryNode directoryNode = new DirectoryNode();
                directoryNode.Path = subdirectory;
                directoryNode.Name = relativePath;

                EnumerateContent(directoryNode);
                if (directoryNode.ContentNodes.Count > 0 || directoryNode.DirectoryNodes.Count > 0)
                    node.DirectoryNodes.Add(directoryNode);
            }
        }

		/// <summary>
		/// Attempts to load the specified content file using the ContentManager.
		/// Returns the loaded object if successful, null otherwise.
		/// </summary>
		/// <param name="contentFile">The content file to load</param>
		/// <returns>the loaded object if successful, null otherwise</returns>
        private static object TryLoadContent(string contentFile)
        {
            try
            {
                return contentManager.Load<object>(contentFile);
            }
            catch (ContentLoadException)
            {
                return null;
            }
        }

        private string selectedContentFile;
        private Type contentType;
        private string imageKey = "Default";

		/// <summary>
		/// Gets the currently selected content file
		/// </summary>
        public string SelectedContentFile
        {
            get { return selectedContentFile; }
        }

        /// <summary>
        /// Creates an instance of ContentBrowser
        /// </summary>
        /// <param name="value">The default content filename</param>
        /// <param name="contentType">The type of content to browse</param>
        public ContentBrowserDialog(String value, Type contentType)
        {
            InitializeComponent();

            selectedContentFile = value;
            this.contentType = contentType;

            if (contentType == typeof(Model))
                imageKey = "Model";
            else if (contentType == typeof(Effect))
                imageKey = "Effect";
            else if (contentType == typeof(Model))
                imageKey = "Model";
            else if (contentType == typeof(SpriteFont))
                imageKey = "SpriteFont";
            else if (contentType == typeof(Texture)
                    || contentType == typeof(Texture2D)
                    || contentType == typeof(Texture3D)
                    || contentType == typeof(TextureCube))
                imageKey = "Texture";

			if (rootNode != null)
				BuildTree(treeView.Nodes, rootNode);            
			else
				Debug.Assert(false, "The ContentBrowser hs not been initialized!");				
        }

		/// <summary>
		/// Builds the TreeView nodes from the tree of enumerated content and directories.
		/// </summary>
		/// <param name="nodeCollection">The TreeNodeCollection that new nodes should be added to</param>
		/// <param name="directoryNode">The DirectoryNode representing the directory being added to the tree</param>
        private void BuildTree(TreeNodeCollection nodeCollection, DirectoryNode directoryNode)
        {
            TreeNode treeNode = new TreeNode(directoryNode.Name);
            treeNode.Tag = directoryNode;
            treeNode.ImageKey = "FolderClosed";
            treeNode.SelectedImageKey = "FolderClosed";
            nodeCollection.Add(treeNode);

            foreach (DirectoryNode node in directoryNode.DirectoryNodes)            
                BuildTree(treeNode.Nodes, node);

            foreach (ContentNode node in directoryNode.ContentNodes)
            {
                if (node.Type == contentType)
                {
                    TreeNode contentTreeNode = new TreeNode(node.Name);
                    contentTreeNode.Tag = node;
                    contentTreeNode.ImageKey = imageKey;
                    contentTreeNode.SelectedImageKey = imageKey;
					treeNode.Nodes.Add(contentTreeNode);

                    if (node.Path == selectedContentFile)
                        treeView.SelectedNode = contentTreeNode;
                }
            }
        }

		/// <summary>
		/// Called after a node is selected in the TreeView
		/// </summary>
		/// <param name="sender">The TreeView that raised the event</param>
		/// <param name="e">Event arguments</param>
        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag is ContentNode)
            {
                ContentNode node = e.Node.Tag as ContentNode;
                selectedContentFile = node.Path;
            }
        }

        /// <summary>
        /// Called after a node is expanded in the TreeView
        /// </summary>
        /// <param name="sender">The TreeView that raised the event</param>
        /// <param name="e">Event arguments</param>
        private void treeView_AfterExpand(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag is DirectoryNode)
            {
                e.Node.ImageKey = "FolderOpen";
                e.Node.SelectedImageKey = e.Node.ImageKey;
            }
        }

        /// <summary>
        /// Called after a node is collapsed in the TreeView
        /// </summary>
        /// <param name="sender">The TreeView that raised the event</param>
        /// <param name="e">Event arguments</param>
        private void treeView_AfterCollapse(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag is DirectoryNode)
            {
                e.Node.ImageKey = "FolderClosed";
                e.Node.SelectedImageKey = e.Node.ImageKey;
            }
        }   

		/// <summary>
		/// Stores information about a directory in the content folder, as well
		/// as content files that belong to that particular directory.
		/// </summary>
        private class DirectoryNode
        {
            public List<DirectoryNode> DirectoryNodes = new List<DirectoryNode>();
            public List<ContentNode> ContentNodes = new List<ContentNode>();
            public string Name;
            public string Path;
        }

		/// <summary>
		/// Stores information about a particular content file
		/// </summary>
        private class ContentNode
        {
            public Type Type;
            public string Name;
            public string Path;
        }             
    }


    /// <summary>
    /// Used to allow the user to browse for content in the PropertyGrid
    /// </summary>
    /// <typeparam name="T">The Type of content to display in the ContentBrowser</typeparam>
    public class ContentBrowserEditor<T> : UITypeEditor
    {
		/// <summary>
		/// Gets the edit style for the editor
		/// </summary>
		/// <param name="context">Editor context</param>
		/// <returns>The editor style, always returns Modal</returns>
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {            
            return UITypeEditorEditStyle.Modal;
        }

		/// <summary>
		/// Allows the user to edit the value by bringing up the 
		/// ContentBrowserDialog dialog.
		/// </summary>
		/// <param name="context">The editor context</param>
		/// <param name="provider">Service provider</param>
		/// <param name="value">The current value of the property</param>
		/// <returns>The new value of the property</returns>
        public override object EditValue(ITypeDescriptorContext context,
                                            IServiceProvider provider,
                                            object value)
        {
            IWindowsFormsEditorService editorService = null;

            if (provider != null)
            {
                editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
            }

            if (editorService != null)
            {
                // Pop up our dialog
                ContentBrowserDialog browser = new ContentBrowserDialog((string)value, typeof(T));

                if (editorService.ShowDialog(browser) == DialogResult.OK)
                    value = browser.SelectedContentFile;
            }

            return value;
        }        
    }
}